//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Polish format parser.
//

# include "StdAfx.h"
# include "Polish.h"
# include "ParseHelpers.h"
# include "Status.h"
# include "Map.h"
# include "Types.h"
# include "GarminTypes.h"
# include "NavitelTypes.h"
# include "RussaTypes.h"
# include "Load.h"
# include "Log.h"
# include "Ellipsoid.h"
# include "Datum.h"
# include "fast_floor.h"
# include "Globals.h"

class CTxtLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

  private:
	enum SectionType {
		ST_NONE, ST_NONE_START,
		ST_IMG_ID,
		ST_POI, ST_RGN20, ST_POLYLINE, ST_POLYGON,
		ST_PLT, ST_WPT, ST_SHP, ST_DBX, ST_FILE, ST_DICTIONARY, ST_MAP, ST_BACKGROUND, ST_DEFINITIONS,
		ST_COUNTRIES, ST_REGIONS, ST_CITIES, ST_HIGHWAYS, ST_ZIPCODES, ST_RESTRICTION,
		ST_UNKNOWN
	} m_GroupType;

	bool ParseCustomDatum (const char * _pValue, const char * _pEnd);
	bool ParseType   (const char * _pValue, const char * _pEnd, const char ** _ppEnd, unsigned long & _ulType);
	bool ParseReal   (const char * _pValue, const char * _pEnd, const char ** _ppEnd, float & _fRet);
	bool ParsePoint  (const char * _pValue, const char * _pEnd, point_t & _pt, const char ** _ppEnd);
	void ParsePoints (const char * _pValue, const char * _pEnd, points_t & _points);
	bool ParseRouteParam (const char * _pValue, const char * _pEnd, rgn_extra_t & _extra);
	bool ParseEntryPoint (const char * _pValue, const char * _pEnd, rgn_extra_t & _extra);
	const char * ParseLine (const char * _p);
	void AddRgn (SectionType _gt, RGN_KIND _Kind, const char * _p);
	void DetectHoles (rgn_elements_t & _elements);

	rgnode_t * ParseRGNode  (const char * _pLine, const char * _pNextLine, const char * & _p, size_t _cPoints, size_t & _cPoint);
	void       ParseNumbers (const char * _pLine, const char * _pNextLine, const char * & _p, size_t _cElement);

	void UpdateNumbers ();

	void AssignCity ();
	void _BuildFullCityName (const string_t & _strCityName, string_t & _strRegionName, string_t & _strCountryName, string_t & _strFullName);
	city_t * _GetCity (const string_t & _strCityName, string_t & _strRegionName, string_t & _strCountryName);
	city_t * _GetCity (const char * & _p, const char * _pNextLine);
	zip_t  * _GetZip  (const char * _strZipCode, size_t _cLen);

	bool OmitComma (const char * & _p, const char * _pEnd);

	map_t * m_pMap;
	const char * m_pStart, * m_pEnd;

	file_ref_t * m_pFileRef;

	// The layer of the current region.
	RGN_KIND m_Kind;
	// The counter of current line.
	size_t m_cFileLine;
	// The counter of regions.
	size_t m_cOrdinal;
	// True if [IMG ID] is defined.
	bool m_bHeader;
	// Characters per point in source text.
	size_t m_cPointLen;
	// Pointer to the probable comment beginning.
	const char * m_pComment;
	// Pointer to end of last section.
	const char * m_pLastSectionEnd;

	// The max number of digits in mantissa.
	size_t m_cPrecOrder;

	// The counter used to omit some SetProgress() calls.
	size_t m_cSetProgress;

	// The buffer of elements used for allocation optimization.
	std::vector<rgn_element_t> m_elements;
	size_t m_cElements;

	// Postal address data.
	std::vector<country_t *> m_vecCountries;
	std::vector<region_t *>  m_vecRegions;
	std::vector<city_t *>    m_vecCities;
	std::vector<zip_t *>     m_vecZips;
	std::vector<highway_t *> m_vecHighways;
	size_t m_cCountryIndex;
	size_t m_cRegionIndex;
	size_t m_cCityIndex;
	size_t m_cZipIndex;
	size_t m_cHighwayIndex;

	typedef std::map<string_t, city_t *> name2city_t;
	name2city_t m_name2city;
	typedef std::map<string_t, zip_t *> name2zip_t;
	name2zip_t m_name2zip;
	string_t m_strCityName;
	string_t m_strRegionName;
	string_t m_strCountryName;
	string_t m_strDefaultCityName;
	string_t m_strDefaultRegionName;
	string_t m_strDefaultCountryName;

	// Routing data.
	typedef std::map<DWORD, rgn_t *> RoadID2Object_t;
	RoadID2Object_t m_RoadID2Object;

	typedef std::map<DWORD, rgnode_t *> NodeID2Node_t;
	NodeID2Node_t m_NodeID2Node;
	rgnode_t * GetRGNode (DWORD _dwNodeID) {
		const NodeID2Node_t::const_iterator i = m_NodeID2Node.find (_dwNodeID);
		if (i == m_NodeID2Node.end ()) {
			rgnode_t & rgnode = m_pMap->CreateNode (_dwNodeID);
			m_NodeID2Node [_dwNodeID] = & rgnode;

			return & rgnode;
		} else
			return i->second;
	}

	rgrestriction_t * m_pLastRestriction;

	std::auto_ptr<CTransformationBase> m_pDatumTransformation;
};

DECLARE_DEFAULT_MAP_LOADER (CTxtLoader, "polish", "mp;txt", "Polish format (*.mp;*.txt)");

static
const char * _GetValue (const char * _pKey, const char * _pEnd) {
	if (_pKey >= _pEnd)
		return _pEnd;
	const char * const p = reinterpret_cast<const char *> (::memchr (_pKey, '=', _pEnd - _pKey));
	return p ? p + 1 : _pEnd;
}

static
const char * _FindDelimiter (const char * _pBegin, const char * _pEnd, const char * _strDelimiter, size_t _cDelimiterLen) {
	assert (_cDelimiterLen >= 2);
	assert (_pBegin <= _pEnd);
	if (_pBegin >= _pEnd)
		return _pEnd;

	const char * p = _pBegin;
	while (p + _cDelimiterLen <= _pEnd) {
		p = reinterpret_cast<const char *> (::memchr (p, * _strDelimiter, _pEnd - p));
		if (p == NULL)
			return _pEnd;
		if (::_strnicmp (p + 1, _strDelimiter + 1, _cDelimiterLen - 1) == 0)
			return p;
		++ p;
	}
	return _pEnd;
}

bool CTxtLoader::OmitComma (const char * & _p, const char * _pEnd) {
	OmitSpaces (& _p, _pEnd);
	if (* _p != ',')
		return false;
	++ _p;
	OmitSpaces (& _p, _pEnd);
	return true;
}

bool CTxtLoader::ParseCustomDatum (const char * _pValue, const char * _pEnd) {
	const char * p = _pValue;
	OmitSpaces (& p, _pEnd);

	// DX
	float fDX = 0;
	if (! ParseReal (p, _pEnd, & p, fDX))
		return false;

	if (! OmitComma (p, _pEnd))
		return false;

	// DY
	float fDY = 0;
	if (! ParseReal (p, _pEnd, & p, fDY))
		return false;

	if (! OmitComma (p, _pEnd))
		return false;


	// DZ
	float fDZ = 0;
	if (! ParseReal (p, _pEnd, & p, fDZ))
		return false;

	if (! OmitComma (p, _pEnd))
		return false;

	// The ellipsoid semi-major axis.
	float fA = 0;
	if (! ParseReal (p, _pEnd, & p, fA))
		return false;
	if (::fabs (fA - 6400000) > 500000)
		ReportWarning (_pValue - m_pStart, "The semi-major axis is expected to be about 6400000 m.");

	if (! OmitComma (p, _pEnd))
		return false;

	// The ellipsoid flattening.
	float fInvFlattening = 0;
	if (! ParseReal (p, _pEnd, & p, fInvFlattening))
		return false;
	if (::fabs (fInvFlattening - 300) > 10)
		ReportWarning (_pValue - m_pStart, "The inverse flattening is expected to be about 300.");

	CEllipsoid e (NULL, fA, 1/fInvFlattening);
	m_pDatumTransformation = std::auto_ptr<CTransformationBase> (new CMolodenskyTransformation (e, c_eWGS84, fDX, fDY, fDZ));

	return true;
}

bool CTxtLoader::ParseType (const char * _pValue, const char * _pEnd, const char ** _ppEnd, unsigned long & _ulType) {
	const char * p = _pValue;

	// Omit spaces.
	while (p < _pEnd && * p == ' ')
		++ p;

	if (p + 1 < _pEnd && p [0] == '0' && p [1] == 'x')
		return ParseHex (p + 2, _pEnd, _ppEnd, _ulType);
	else
		return ParseDecU (p, _pEnd, _ppEnd, _ulType);
}

bool CTxtLoader::ParseReal (const char * _pValue, const char * _pEnd, const char ** _ppEnd, float & _fRet) {
	float r = 0;
	bool bMinus = false;
	bool bHasDigits = false;
	const char * p = _pValue;

	// Get sign.
	if (p < _pEnd && * p == '-') {
		++ p;
		bMinus = true;
	}

	// Get whole part of the digit.
	while (p < _pEnd && '0' <= * p && * p <= '9') {
		r *= 10;
		r += (* p) - '0';
		bHasDigits = true;

		++ p;
	}

	// Get decimal dot.
	if (p < _pEnd && * p != '.') {
		* _ppEnd = p;
		_fRet = bMinus ? -r : r;
		return bHasDigits;
	}
	++ p;
	const char * pFraction = p;

	// Get fractional part of the digit.
	float f = 0;
	float pw = 0.1f;
	while (p < _pEnd && '0' <= * p && * p <= '9') {
		f += ((* p) - '0')*pw;
		pw *= .1f;
		bHasDigits = true;

		++ p;
	}

	// Update max precision.
	if ((int) m_cPrecOrder < p - pFraction)
		m_cPrecOrder = p - pFraction;

	* _ppEnd = p;
	_fRet = bMinus ? -(r + f) : (r + f);

	return bHasDigits;
}

bool CTxtLoader::ParsePoint (const char * _pValue, const char * _pEnd, point_t & _pt, const char ** _ppEnd) {
	const char * p = _pValue;

	// Omit spaces.
	while (p < _pEnd && * p == ' ')
		++ p;

	// Get opening bracket.
	if (p >= _pEnd || * p != '(') {
		* _ppEnd = _pEnd;
		return false;
	}
	++ p;

	while (p < _pEnd && * p == ' ')
		++ p;

	// Get latitude.
	if (! ParseReal (p, _pEnd, & p, _pt.y)) {
		ReportWarning (_pValue - m_pStart, "Latitude value is expected. The point will be ignored.");
		* _ppEnd = _pEnd;
		return false;
	}

	// Omit spaces.
	while (p < _pEnd && * p == ' ')
		++ p;

	// Get delimiting comma.
	if (p >= _pEnd || * p != ',') {
		ReportWarning (_pValue - m_pStart, "Comma between latitude and longitude is expected. The point will be ignored.");
		* _ppEnd = _pEnd;
		return false;
	}
	++ p;

	// Omit spaces.
	while (p < _pEnd && * p == ' ')
		++ p;

	// Get longitude.
	if (! ParseReal (p, _pEnd, & p, _pt.x)) {
		ReportWarning (_pValue - m_pStart, "Longitude value is expected. The point will be ignored.");
		* _ppEnd = _pEnd;
		return false;
	}

	// Omit spaces.
	while (p < _pEnd && * p == ' ')
		++ p;

	// Get closing bracket.
	if (p >= _pEnd || * p != ')') {
		ReportWarning (_pValue - m_pStart, "Closing bracket for point is expected. The point will be ignored.");
		* _ppEnd = _pEnd;
		return false;
	}
	++ p;
	
	* _ppEnd = p;
/*
	// Check coordinates.
	if (90 <= _pt.y || _pt.y <= -90) {
		ReportWarning (_pValue - m_pStart, "Invalid latitude value: %0.5f. The point will be ignored.", _pt.y);
		return false;
	}
	if (180 < _pt.x || _pt.x < -180) {
		ReportWarning (_pValue - m_pStart, "Invalid longitude value: %0.5f. The point will be ignored.", _pt.x);
		return false;
	}
*/
	return true;
}

void CTxtLoader::ParsePoints (const char * _pValue, const char * _pEnd, points_t & _points) {
	for (const char * p = _pValue; p < _pEnd; ++ p) {
		point_t pt;
		if (ParsePoint (p, _pEnd, pt, & p))
			_points.push_back (pt);

		// Get point-delimiting comma.
		while (p < _pEnd && * p != ',')
			++ p;
	}
}

bool CTxtLoader::ParseRouteParam (const char * _pValue, const char * _pEnd, rgn_extra_t & _extra) {
	const char * p = _pValue;

	// Speed limit.
	unsigned long ulSpeedLimit = 0;
	if (! ParseDecU (p, _pEnd, & p, ulSpeedLimit))
		return false;
	if (ulSpeedLimit > 7) {
		ReportWarning (_pValue - m_pStart, "'RouteParam' - unexpected value for speed limit. Reset to 7 (no limit).");
		ulSpeedLimit = 7;
	}
	if (! OmitComma (p, _pEnd))
		return false;

	// Route class.
	unsigned long ulRouteClass = 0;
	if (! ParseDecU (p, _pEnd, & p, ulRouteClass))
		return false;
	if (ulRouteClass > 4) {
		ReportWarning (_pValue - m_pStart, "'RouteParam' - unexpected value for route class. Reset to 4.");
		ulRouteClass = 4;
	}
	if (! OmitComma (p, _pEnd))
		return false;

	// One way.
	const bool bOneWay = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// Toll.
	const bool bToll = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No emergency.
	const bool bNoEmergency = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No delivery.
	const bool bNoDelivery = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No car.
	const bool bNoCar = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No bus.
	const bool bNoBus = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No taxi.
	const bool bNoTaxi = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No pedestrian.
	const bool bNoPedestrian = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No bicycle.
	const bool bNoBicycle = * (p ++) != '0';
	if (! OmitComma (p, _pEnd))
		return false;

	// No truck.
	const bool bNoTruck = * (p ++) != '0';

	_extra.fSpeedLimit   = ulSpeedLimit;
	_extra.fRouteClass   = ulRouteClass;
	_extra.bOneWay       = bOneWay;
	_extra.bToll         = bToll;
	_extra.bNoEmergency  = bNoEmergency;
	_extra.bNoDelivery   = bNoDelivery;
	_extra.bNoCar        = bNoCar;
	_extra.bNoBus        = bNoBus;
	_extra.bNoTaxi       = bNoTaxi;
	_extra.bNoPedestrian = bNoPedestrian;
	_extra.bNoBicycle    = bNoBicycle;
	_extra.bNoTruck      = bNoTruck;

	return true;
}

bool CTxtLoader::ParseEntryPoint (const char * _pValue, const char * _pEnd, rgn_extra_t & _extra) {
	_extra.bEntryPointDefined = true;

	const char * pEnd;
	return ParsePoint (_pValue, _pEnd, _extra.ptEntryPoint, &pEnd);
}

void CTxtLoader::AssignCity () {
	if (m_strCityName.empty ()) {
		if (m_strDefaultCityName.empty ())
			return;
		m_strCityName = m_strDefaultCityName;
	}

	rgn_t & rgn = m_pMap->layers [m_Kind].rgns.back ();
	rgn.pExtra->pCity = _GetCity (m_strCityName, m_strRegionName, m_strCountryName);

	m_strCityName   .clear ();
	m_strRegionName .clear ();
	m_strCountryName.clear ();
}

void CTxtLoader::_BuildFullCityName (const string_t & _strCityName, string_t & _strRegionName, string_t & _strCountryName, string_t & _strFullName) {
	_strFullName = _strCityName;

	if (_strRegionName.empty ())
		_strRegionName = m_strDefaultRegionName;
	if (! _strRegionName.empty ()) {
		_strFullName.append (1, '|');
		_strFullName.append (_strRegionName);
	}

	if (_strCountryName.empty ())
		_strCountryName = m_strDefaultCountryName;
	if (! _strCountryName.empty ()) {
		_strFullName.append (1, '|');
		_strFullName.append (_strCountryName);
	}
}

city_t * CTxtLoader::_GetCity (const string_t & _strCityName, string_t & _strRegionName, string_t & _strCountryName) {
	if (m_name2city.empty () && ! m_pMap->Cities.empty ()) {
		// Fill m_name2city with data previously loaded from [CITIES].
		for (cities_t::iterator cti = m_pMap->Cities.begin (); cti != m_pMap->Cities.end (); ++ cti) {
			city_t & city = * cti;
			if (city.IsDeleted ())
				continue;

			string_t strRegionName  = city.pRegion->strName;
			string_t strCountryName = city.pRegion->pCountry->strName;
			string_t strFullName;
			_BuildFullCityName (city.strName, strRegionName, strCountryName, strFullName);
			m_name2city.insert (name2city_t::value_type (strFullName, & city));
		}
	}

	string_t strFullName;
	_BuildFullCityName (_strCityName, _strRegionName, _strCountryName, strFullName);

	if (strFullName.empty () || _strRegionName.empty () || _strCountryName.empty ())
		return NULL;

	const name2city_t::const_iterator i = m_name2city.find (strFullName);
	if (i != m_name2city.end ())
		return i->second;

	// Find country.
	country_t * pCountry = NULL;
	for (countries_t::iterator ci = m_pMap->Countries.begin (); ci != m_pMap->Countries.end (); ++ ci) {
		country_t & country = * ci;
		if (country.IsDeleted ())
			continue;

		if (country.strName == _strCountryName) {
			pCountry = & country;
			break;
		}
	}
	if (pCountry == NULL) {
		m_pMap->Countries.push_back (country_t ());
		pCountry = & m_pMap->Countries.back ();
		pCountry->strName.swap (_strCountryName);
	}

	// Find region.
	region_t * pRegion = NULL;
	for (regions_t::iterator ri = m_pMap->Regions.begin (); ri != m_pMap->Regions.end (); ++ ri) {
		region_t & region = * ri;
		if (region.IsDeleted ())
			continue;

		if (region.strName == _strRegionName && region.pCountry == pCountry) {
			pRegion = & region;
			break;
		}
	}
	if (pRegion == NULL) {
		m_pMap->Regions.push_back (region_t ());
		pRegion = & m_pMap->Regions.back ();
		pRegion->pCountry = pCountry;
		pRegion->strName.swap (_strRegionName);
	}

	// Create city.
	m_pMap->Cities.push_back (city_t ());
	city_t & city = m_pMap->Cities.back ();
	city.strName = _strCityName;
	city.pRegion = pRegion;

	m_name2city.insert (name2city_t::value_type (strFullName, & city));

	return & city;
}

void CTxtLoader::AddRgn (SectionType _gt, RGN_KIND _Kind, const char * _p) {
	if (! m_bHeader)
		ReportWarning (_p - m_pStart, "[IMG ID] section should be defined first.");

	m_GroupType = _gt;
	m_Kind = _Kind;

	// Create new rgn.
	rgn_t & rgn = m_pMap->CreateRgn (_Kind);

	rgn.cBeginPos = (m_pComment ? m_pComment : _p) - m_pStart;
	rgn.Kind      = m_Kind;
	rgn.cFileLine = m_cFileLine;
//	rgn.cOrdinal  = m_cOrdinal ++;
	rgn.pFileRef  = m_pFileRef;

	if (m_pComment) {
		const size_t cCommentLen = _p - m_pComment;
		char * const strComment = (char *) ::alloca (sizeof (char)*(cCommentLen));
		size_t c = 0;

		// Omit line-leading semicolons.
		bool bFirstSemicolonInLine = true;
		for (const char * p = m_pComment; p < _p; p = ::CharNext (p)) {
			const char ch = * p;
			if (ch == '\0') {
				// NOTE: ::CharNext(p) returns p in this case causing infinite loop.
				// Let's avoid this.
				++ p;
				continue;
			}

			if (! bFirstSemicolonInLine || ch != ';') {
				assert (c < cCommentLen);
				if (c >= cCommentLen)
					break;
				strComment [c ++] = ch; // TODO: copy all bytes of char in MSCS.
			}

			bFirstSemicolonInLine = ch == '\n' || ch == '\r';
		}
		assert (c <= cCommentLen);

		rgn.strComment.assign (strComment, c);
	}
}

void CTxtLoader::DetectHoles (rgn_elements_t & _elements) {
	size_t cOuterElement = 0;

	const size_t cElements = _elements.size ();
	for (size_t cElement = 0; cElement < cElements; ++ cElement) {
		rgn_element_t & element = _elements [cElement];
		const points_t & points = element.points;
		const size_t cPoints = points.size ();

		// NOTE: count element's area before 'holes' detection.
		element.fLengthOrArea = GetArea (points);

		if (cElement > 0 && cPoints >= 3) {
			if (element.IsHoleOf (_elements [cOuterElement])) {
				element.bHole = true;
			} else {
				element.bHole = false;
				cOuterElement = cElement;
			}
		} else
			element.bHole = false;
	}
}

rgnode_t * CTxtLoader::ParseRGNode (const char * _pLine, const char * _pNextLine, const char * & _p, size_t _cPoints, size_t & _cPoint) {
	unsigned long ulPoint = 0;
	if (! ParseDecU (_p, _pNextLine, & _p, ulPoint)) {
		ReportWarning (_pLine - m_pStart, "Unexpected point index.");
		return NULL;
	}
	if (ulPoint >= _cPoints) {
		ReportWarning (_pLine - m_pStart, "Unexpected index of point.");
		return NULL;
	}

	OmitComma (_p, _pNextLine);

	unsigned long ulNodeID = 0;
	if (! ParseDecU (_p, _pNextLine, & _p, ulNodeID)) {
		ReportWarning (_pLine - m_pStart, "Unexpected node ID.");
		return NULL;
	}

	bool bIsExternal = false;
	if (OmitComma (_p, _pNextLine)) {
   		unsigned long ulIsExternal = 0;
		if (ParseDecU (_p, _pNextLine, & _p, ulIsExternal))
			bIsExternal = ulIsExternal != 0;
		else
			ReportWarning (_pLine - m_pStart, "Unexpected format of 'NodX'.");
	}

	rgnode_t * const pNode = GetRGNode (ulNodeID);
	assert (pNode != NULL);
	if (pNode != NULL)
		pNode->bIsExternal = bIsExternal;

	_cPoint = ulPoint;
	return pNode;
}

static
void _FindNextDelimiter (const char * & _p, const char * _pNextLine) {
	while (_p < _pNextLine && * _p != ',' && * _p != ')' && * _p != '\r' && * _p != '\n')
		++ _p;
}

static
bool _ParseNumber (const char * & _p, const char * _pEnd, unsigned long & _ulNumber) {
	if (_p [0] == '-') {
		++ _p;
		_FindNextDelimiter (_p, _pEnd);
		_ulNumber = 0;
		return true;
	}
	if (! ParseDecU (_p, _pEnd, & _p, _ulNumber))
		return false;
	return true;
}

void CTxtLoader::ParseNumbers (const char * _pLine, const char * _pNextLine, const char * & _p, size_t _cElement) {
	if (m_elements.empty ())
		return;
	const size_t cPoints = m_elements [_cElement].points.size ();

	// Point.
	unsigned long ulPoint = 0;
	if (! ParseDecU (_p, _pNextLine, & _p, ulPoint)) {
		ReportWarning (_pLine - m_pStart, "Unexpected point index.");
		return;
	}
	if (ulPoint >= cPoints) {
		ReportWarning (_pLine - m_pStart, "Unexpected index of point.");
		return;
	}

	rgn_element_extra_t & ee = * m_elements [_cElement].pExtra;
	point_extras_t & PointExtras = ee.PointExtras;
	if (cPoints >= PointExtras.size ())
		PointExtras.resize (cPoints);
	numbers_t & Numbers = PointExtras [ulPoint].Numbers;

	OmitComma (_p, _pNextLine);

	// Left parity.
	switch (* _p) {
		case 'N':
		case 'n': Numbers.wLeftParity = 0; break;
		case 'E':
		case 'e': Numbers.wLeftParity = 1; break;
		case 'O':
		case 'o': Numbers.wLeftParity = 2; break;
		case 'B':
		case 'b': Numbers.wLeftParity = 3; break;

		default:
			ReportWarning (_pLine - m_pStart, "Unexpected format.");
			return;
	}
	++ _p;

	OmitComma (_p, _pNextLine);

	// Left from.
	unsigned long ulLeftFrom = 0;
	if (! _ParseNumber (_p, _pNextLine, ulLeftFrom)) {
		ReportWarning (_pLine - m_pStart, "Unexpected format.");
		return;
	}
	Numbers.wLeftAfter = ulLeftFrom;

	OmitComma (_p, _pNextLine);

	// Left to.
	unsigned long ulLeftTo = 0;
	if (! _ParseNumber (_p, _pNextLine, ulLeftTo)) {
		ReportWarning (_pLine - m_pStart, "Unexpected format.");
		return;
	}
	Numbers.wLeftBefore = ulLeftTo; // NOTE: to be copied to previous node later.
	Numbers.bHasNumbers = 1;

	OmitComma (_p, _pNextLine);

	// Right parity.
	switch (* _p) {
		case 'N':
		case 'n': Numbers.wRightParity = 0; break;
		case 'E':
		case 'e': Numbers.wRightParity = 1; break;
		case 'O':
		case 'o': Numbers.wRightParity = 2; break;
		case 'B':
		case 'b': Numbers.wRightParity = 3; break;

		default:
			ReportWarning (_pLine - m_pStart, "Unexpected format.");
			return;
	}
	++ _p;

	OmitComma (_p, _pNextLine);

	// Right from.
	unsigned long ulRightFrom = 0;
	if (! _ParseNumber (_p, _pNextLine, ulRightFrom)) {
		ReportWarning (_pLine - m_pStart, "Unexpected format.");
		return;
	}
	Numbers.wRightAfter = ulRightFrom;

	OmitComma (_p, _pNextLine);

	// Right to.
	unsigned long ulRightTo = 0;
	if (! _ParseNumber (_p, _pNextLine, ulRightTo)) {
		ReportWarning (_pLine - m_pStart, "Unexpected format.");
		return;
	}
	Numbers.wRightBefore = ulRightTo; // NOTE: to be copied to previous node later.

	OmitComma (_p, _pNextLine);

	// Left zip.
	const char * const strZipLeft = _p;
	_FindNextDelimiter (_p, _pNextLine);
	Numbers.pZipLeftAfter = _GetZip (strZipLeft, _p - strZipLeft);

	OmitComma (_p, _pNextLine);

	// Right zip.
	const char * const strZipRight = _p;
	_FindNextDelimiter (_p, _pNextLine);
	Numbers.pZipRightAfter = _GetZip (strZipRight, _p - strZipRight);

	OmitComma (_p, _pNextLine);

	// Left city.
	Numbers.pCityLeftAfter  = _GetCity (_p, _pNextLine);
	OmitComma (_p, _pNextLine);

	// Right city.
	Numbers.pCityRightAfter = _GetCity (_p, _pNextLine);
}

void CTxtLoader::UpdateNumbers () {
	if (m_Kind != rkPolyline)
		return;

	rgn_t & rgn = m_pMap->layers [m_Kind].rgns.back ();	
	const size_t cElements = rgn.elements.size ();
	for (size_t cElement = 0; cElement < cElements; ++ cElement) {
		rgn_element_t & element = rgn.elements [cElement];
		if (! element.pExtra)
			continue;

 		point_extras_t & PointExtras = element.pExtra->PointExtras;
		if (! HasNumbers (PointExtras))
			continue;

		const int nPoints = PointExtras.size ();
		if (nPoints == 0)
			continue;
		numbers_t * pNextNumbers = NULL;
		for (int nPoint = nPoints - 1; nPoint >= 0; -- nPoint) {
			numbers_t & Numbers = PointExtras [nPoint].Numbers;
			if (! Numbers.bHasNumbers && nPoint != nPoints - 1)
				continue;

			if (pNextNumbers) {
				pNextNumbers->wLeftBefore  = Numbers.wLeftBefore;
				pNextNumbers->wRightBefore = Numbers.wRightBefore;
			}

			pNextNumbers = & Numbers;
		}

		numbers_t & NumbersFirst = PointExtras [0].Numbers;
		NumbersFirst.wLeftBefore  = 0;
		NumbersFirst.wRightBefore = 0;

		numbers_t & NumbersLast = PointExtras [nPoints - 1].Numbers;
		NumbersLast.bHasNumbers  = true;
		NumbersLast.wLeftParity  = 0;
		NumbersLast.wRightParity = 0;
	}
}

city_t * CTxtLoader::_GetCity (const char * & _p, const char * _pNextLine) {
	// City.
	const char * const strCity0 = _p;
	_FindNextDelimiter (_p, _pNextLine);
	if (_p == strCity0 + 2 && strCity0 [0] == '-' && strCity0 [1] == '1')
		return NULL;

	string_t strCity (strCity0, _p);
	OmitComma (_p, _pNextLine);

	// Region.
	const char * const strRegion0 = _p;
	_FindNextDelimiter (_p, _pNextLine);

	string_t strRegion (strRegion0, _p);
	OmitComma (_p, _pNextLine);

	// Country.
	const char * const strCountry0 = _p;
	_FindNextDelimiter (_p, _pNextLine);

	string_t strCountry (strCountry0, _p);
	OmitComma (_p, _pNextLine);

	return _GetCity (strCity, strRegion, strCountry);
}

zip_t * CTxtLoader::_GetZip (const char * _strZipCode, size_t _cLen) {
	if (_cLen == 0)
		return NULL;
	if (_cLen == 2 && _strZipCode [0] == '-' && _strZipCode [1] == '1')
		return NULL;

	string_t strZipCode (_strZipCode, _cLen);
	const name2zip_t::iterator zi = m_name2zip.find (strZipCode);
	if (zi == m_name2zip.end ()) {
		m_pMap->Zips.push_back (zip_t ());
		zip_t & zip = m_pMap->Zips.back ();
		zip.strName.swap (strZipCode);

		m_vecZips.push_back (& zip);
		m_name2zip.insert (name2zip_t::value_type (zip.strName, & zip));

		return & zip;
	} else
		return zi->second;
}

const char * CTxtLoader::ParseLine (const char * _p) {
	const char * const pNextLine = FindNextLine (_p, m_pEnd);

	switch (_p [0]) {
		case ';':
			if (_p + 1 < m_pEnd && _p [1] == '@') {
				// Comment contains extended data.
				const bool bImage = HasPrefix (_p + 2, pNextLine, "IMAGE,\"", 7);
				const bool bFile  = HasPrefix (_p + 2, pNextLine, "FILE,\"", 6);
				if (bImage || bFile) {
					const char * strFileNameBegin = reinterpret_cast<const char *> (::memchr (_p + 2, '\"', pNextLine - _p - 1));
					if (strFileNameBegin)
						++ strFileNameBegin;
					else {
						ReportWarning (_p - m_pStart, "Unexpected format of attachment definition.");
						break;
					}
					const char * const strFileNameEnd = reinterpret_cast<const char *> (::memchr (strFileNameBegin, '\"', pNextLine - strFileNameBegin - 1));
					if (strFileNameEnd == NULL || strFileNameEnd > strFileNameBegin + MAX_PATH) {
						ReportWarning (_p - m_pStart, "Unexpected format of attachment definition.");
						break;
					}

					char strFileName [MAX_PATH + 1];
					::memcpy (strFileName, strFileNameBegin, strFileNameEnd - strFileNameBegin);
					strFileName [strFileNameEnd - strFileNameBegin] = '\0';

					ReportText ("");
					ReportText ("Loading attached file '%s'...", strFileName);

					map_t mapAttachment;
					const CFileTypeInfo * pFTI = NULL;
					if (CMapLoader::LoadFile (strFileName, & mapAttachment, & pFTI) != lsOK) {
						ErrorMsgBox ("Can't load the attached file\n%s\n\nPlease see menu 'File | Message Log' for details.", strFileName);
						break;
					}

					// Parse and apply correction to image.
					if (bImage && strFileNameEnd < pNextLine && strFileNameEnd [1] == ',') {
						const char * p = strFileNameEnd + 2;
						OmitSpaces (& p, pNextLine);
						point_t ptCorrectionShift;
						if (::ParseReal (p, pNextLine, & p, ptCorrectionShift.x)) {
							if (* p == ',')
								++ p;
							OmitSpaces (& p, pNextLine);

							if (::ParseReal (p, pNextLine, & p, ptCorrectionShift.y)) {
								if (* p == ',')
									++ p;
								OmitSpaces (& p, pNextLine);

								unsigned long ulVisible = 1;
								if (! ::ParseDecU (p, pNextLine, & p, ulVisible))
									ulVisible = 1;

								for (renderers_t::iterator ri = mapAttachment.renderers.begin (); ri != mapAttachment.renderers.end (); ++ ri) {
									renderer_base_t * const pRenderer = * ri;
									pRenderer->ptCorrectionShift = ptCorrectionShift;
									pRenderer->rect.Shift (ptCorrectionShift.x, ptCorrectionShift.y);
									if (ulVisible == 0)
										pRenderer->bVisible = false;
								}
							}
						}
					}

					if (bFile && strFileNameEnd < pNextLine && strFileNameEnd [1] == ',') {
						const char * p = strFileNameEnd + 2;
						OmitSpaces (& p, pNextLine);

						unsigned long ulVisible = 1;
						if (! ::ParseDecU (p, pNextLine, & p, ulVisible))
							ulVisible = 1;

						if (ulVisible == 0)
							for (file_refs_t::iterator ifr = mapAttachment.file_refs.begin (); ifr != mapAttachment.file_refs.end (); ++ ifr) {
								file_ref_t & fr = * ifr;
								fr.bVisible = false;
							}
					}

					m_pMap->Join (mapAttachment);

					ReportText ("");
				}
			} else {
				// Ordinary comment.
				if (! m_pComment)
					m_pComment = _p;
			}
			break;

		case '\n':
		case '\r':
			break;

		case ' ':
		case '\t':
			ReportWarning (_p - m_pStart, "Line begins with white space. It will be ignored.");
			break;

		case '[': {
			const bool bOrdinaryEndOfSection = HasPrefix (_p + 1, pNextLine, "END", 3);
			const char * pSectionEnd = pNextLine;
			bool bEndOfSection = bOrdinaryEndOfSection;
			if (! bOrdinaryEndOfSection && m_GroupType != ST_NONE && m_GroupType != ST_NONE_START) {
				bEndOfSection = true;
				pSectionEnd = _p;

				ReportWarning (_p - m_pStart, "Previous section has no closing [END].");
				m_pComment = NULL;
//				m_pLastSectionEnd = _p;
			}
			if (bEndOfSection) {
				switch (m_GroupType) {
					case ST_NONE:
					case ST_NONE_START:
						ReportWarning (_p - m_pStart, "[END] has no matching section.");
						break;

					case ST_IMG_ID:
						m_pMap->cHeaderLen = pNextLine - m_pStart;
						break;

					case ST_POI:
					case ST_RGN20:
					case ST_POLYLINE:
					case ST_POLYGON: {
						AssignCity ();

						if (m_cElements) {
							// Copy elements from tmp buffer to region (speed optimization).
							rgn_elements_t & elements = m_pMap->layers [m_Kind].rgns.back ().elements;
							elements.resize (m_cElements);
							for (size_t cElement = 0; cElement < m_cElements; ++ cElement)
								elements [cElement].swap (m_elements [cElement]);
							m_cElements = 0;

							// Detect inner polygons.
							if (m_GroupType == ST_POLYGON)
								DetectHoles (elements);
						}

						m_pMap->layers [m_Kind].rgns.back ().cEndPos = pNextLine - m_pStart;

						UpdateNumbers ();
						break;
					}

					case ST_COUNTRIES:
					case ST_REGIONS:
					case ST_CITIES:
					case ST_ZIPCODES:
					case ST_HIGHWAYS:
						break;

					case ST_PLT:
					case ST_WPT:
					case ST_SHP:
					case ST_DBX:
					case ST_BACKGROUND:
					case ST_DEFINITIONS:
					case ST_FILE:
					case ST_DICTIONARY:
					case ST_MAP:
					case ST_UNKNOWN:
						// Save content of unsupported section.
						m_pMap->ExtraSections.push_back (section_t ());
						section_t & strSection = m_pMap->ExtraSections.back ();
						strSection.strText.assign (m_pLastSectionEnd, pSectionEnd);
						break;
				}

				m_GroupType = ST_NONE;
				m_pComment = NULL;
				m_pLastSectionEnd = pSectionEnd;

				if (bOrdinaryEndOfSection)
					break;
			}

			++ m_cSetProgress;
			if ((m_cSetProgress & 0xFF) == 0xFF)
				SetProgressStatus (fast_floor ((50.f*(_p - m_pStart))/(m_pEnd - m_pStart) + .5f));

			if (HasPrefix (_p + 1, pNextLine, "POI]", 4)) {
				g_bPolish074 = true;
				AddRgn (ST_POI, rkPoint, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "POLYLINE]", 9)) {
				g_bPolish074 = true;
				AddRgn (ST_POLYLINE, rkPolyline, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "POLYGON]", 8)) {
				g_bPolish074 = true;
				AddRgn (ST_POLYGON, rkPolygon, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "RGN10]", 6)) {
				g_bPolish074 = false;
				AddRgn (ST_POI, rkPoint, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "RGN20]", 6)) {
				g_bPolish074 = false;
				AddRgn (ST_RGN20, rkPoint, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "RGN40]", 6)) {
				g_bPolish074 = false;
				AddRgn (ST_POLYLINE, rkPolyline, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "RGN80]", 6)) {
				g_bPolish074 = false;
				AddRgn (ST_POLYGON, rkPolygon, _p);
			} else if (HasPrefix (_p + 1, pNextLine, "IMG ID]", 7)) {
				if (m_bHeader)
					ReportWarning (_p - m_pStart, "[IMG ID] section is already defined.");
				m_GroupType = ST_IMG_ID;
				m_bHeader = true;
			} else if (HasPrefix (_p + 1, pNextLine, "PLT]", 4)) {
				ReportWarning (_p - m_pStart, "[PLT] section is not supported.");
				m_GroupType = ST_PLT;
			} else if (HasPrefix (_p + 1, pNextLine, "WPT]", 4)) {
				ReportWarning (_p - m_pStart, "[WPT] section is not supported.");
				m_GroupType = ST_WPT;
			} else if (HasPrefix (_p + 1, pNextLine, "SHP]", 4)) {
				ReportWarning (_p - m_pStart, "[SHP] section is not supported.");
				m_GroupType = ST_SHP;
			} else if (HasPrefix (_p + 1, pNextLine, "DBX]", 4)) {
				ReportWarning (_p - m_pStart, "[DBX] section is not supported.");
				m_GroupType = ST_DBX;
			} else if (HasPrefix (_p + 1, pNextLine, "BACKGROUND]", 11)) {
				ReportWarning (_p - m_pStart, "[BACKGROUND] section is not supported.");
				m_GroupType = ST_BACKGROUND;
			} else if (HasPrefix (_p + 1, pNextLine, "DEFINITIONS]", 12)) {
				ReportWarning (_p - m_pStart, "[DEFINITIONS] section is not supported.");
				m_GroupType = ST_DEFINITIONS;
			} else if (HasPrefix (_p + 1, pNextLine, "FILE]", 5)) {
				ReportWarning (_p - m_pStart, "[FILE] section is not supported.");
				m_GroupType = ST_FILE;
			} else if (HasPrefix (_p + 1, pNextLine, "DICTIONARY]", 11)) {
				ReportWarning (_p - m_pStart, "[DICTIONARY] section is not supported.");
				m_GroupType = ST_DICTIONARY;
			} else if (HasPrefix (_p + 1, pNextLine, "MAP]", 4)) {
				ReportWarning (_p - m_pStart, "[MAP] section is not supported.");
				m_GroupType = ST_MAP;
			} else if (HasPrefix (_p + 1, pNextLine, "Countries]", 10)) {
				m_GroupType = ST_COUNTRIES;
			} else if (HasPrefix (_p + 1, pNextLine, "Regions]", 8)) {
				m_GroupType = ST_REGIONS;
			} else if (HasPrefix (_p + 1, pNextLine, "Cities]", 7)) {
				m_GroupType = ST_CITIES;
			} else if (HasPrefix (_p + 1, pNextLine, "ZipCodes]", 9)) {
				m_GroupType = ST_ZIPCODES;
			} else if (HasPrefix (_p + 1, pNextLine, "Highways]", 9)) {
				m_GroupType = ST_HIGHWAYS;
			} else if (HasPrefix (_p + 1, pNextLine, "Restrict]", 9)) {
				m_GroupType = ST_RESTRICTION;
			} else {
				const size_t cLen = GetLineLen (_p, pNextLine);
				const string_t str (_p, cLen);
				ReportWarning (_p - m_pStart, "Unknown section type: \'%s\'.", str.c_str ());

				m_GroupType = ST_UNKNOWN;
			}
			
			break;
		}

		default: {
			if (m_GroupType == ST_NONE) {
				const size_t cLen = GetLineLen (_p, pNextLine);
				const string_t str (_p, cLen);
				ReportWarning (_p - m_pStart, "Some text is out of section: \'%s\'.", str.c_str ());

				break;
			}

			const char * const pKey = _p;
			const char * const pValue = _GetValue (_p, pNextLine);

			switch (m_GroupType) {
				case ST_POI:
				case ST_RGN20:
				case ST_POLYLINE:
				case ST_POLYGON: {
					rgn_t & rgn = m_pMap->layers [m_Kind].rgns.back ();
					if (HasPrefix (pKey, pNextLine, "Data", 4)) {
						// Determine zoom level.
						const size_t cLevel = pKey [4] - '0';
						if (! m_pMap->Levels.empty ()) {
							if (cLevel >= m_pMap->Levels.size ()) {
								ReportWarning (_p - m_pStart, "Incorrect level: Data%d. The element will be ignored.", cLevel);
								break;
							}
							if (cLevel == m_pMap->Levels.size () - 1) {
								ReportWarning (_p - m_pStart, "Level%d should be empty. The element will be ignored.", cLevel);
								break;
							}
						}

						if (m_GroupType == ST_POI || m_GroupType == ST_RGN20) {
							// Parse origin.
							point_t point;
							const char * p = NULL;
							if (! ParsePoint (pValue, pNextLine, point, & p))
								break;

							// Create new origin.
							rgn.origins.push_back (rgn_origin_t ());
							rgn_origin_t & origin = rgn.origins.back ();

							origin.cLevel = cLevel;
							origin.point = point;
							origin.cFileOffset = _p - m_pStart;
						} else {
							// Create new element in tmp buffer (speed optimization).
							if (m_cElements >= m_elements.size ())
								m_elements.push_back (rgn_element_t ());
							rgn_element_t & element = m_elements [m_cElements ++];

							element.cLevel = cLevel;
							element.cFileOffset = _p - m_pStart;

							// Parse points.
							points_t & points = element.points;
							// NOTE: reserve predicted size for speed optimization.
							points.reserve ((pNextLine - pValue + 2)/m_cPointLen);
							ParsePoints (pValue, pNextLine, points);

							// Recalculate average point length.
							if (! points.empty ()) {
								m_cPointLen = (pNextLine - pValue + 2)/points.size ();
								if (m_cPointLen == 0)
									m_cPointLen = 22;
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "Origin", 6)) {
						if (m_GroupType == ST_POLYLINE) {
							ReportWarning (_p - m_pStart, "Key 'Origin' is unexpected for [RGN40]. It will be ignored.");
							break;
						} else if (m_GroupType == ST_POLYGON) {
							ReportWarning (_p - m_pStart, "Key 'Origin' is unexpected for [RGN80]. It will be ignored.");
							break;
						}

						// Determine zoom level.
						const size_t cLevel = pKey [6] - '0';
						if (! m_pMap->Levels.empty ()) {
							if (cLevel >= m_pMap->Levels.size ()) {
								ReportWarning (_p - m_pStart, "Incorrect level: Origin%d. The point will be ignored.", cLevel);
								break;
							}
							if (cLevel == m_pMap->Levels.size () - 1) {
								ReportWarning (_p - m_pStart, "Level%d should be empty. The point will be ignored.", cLevel);
								break;
							}
						}

						// Parse origin.
						point_t point;
						const char * p = NULL;
						if (! ParsePoint (pValue, pNextLine, point, & p))
							break;

						// Create new origin.
						rgn.origins.push_back (rgn_origin_t ());
						rgn_origin_t & origin = rgn.origins.back ();

						origin.cLevel = cLevel;
						origin.point = point;
						origin.cFileOffset = _p - m_pStart;
					} else if (HasPrefix (pKey, pNextLine, "Type=", 5)) {
						unsigned long ulType = 0;
						const char * pEnd = NULL;
						if (! ParseType (pValue, pNextLine, & pEnd, ulType)) {
							ReportWarning (_p - m_pStart, "Can't parse type.");
							ulType = 0;
						}
						if (
							(m_pMap->pTypeSet == & g_tsGarmin || m_pMap->pTypeSet == & g_tsNavitel || m_pMap->pTypeSet == & g_tsRussa) &&
							rgn.Kind == rkPoint
						) {
							if (ulType <= 0x00FF)
								ulType <<= 8;

							// Handle obsolete RGN20 points.
							if (m_GroupType == ST_RGN20)
								rgn.dwFlags |= rgn_t::fIndexedCity;
						}
						rgn.SetType (m_pMap->pTypeSet, ulType, m_Kind);
						if (rgn.pTypeDesc->bUnknown)
							ReportWarning (_p - m_pStart, "Unknown type 0x%x (%s).", ulType, g_strKinds [m_Kind]);
					} else if (HasPrefix (pKey, pNextLine, "SubType=", 8) && m_Kind == rkPoint) {
						unsigned long ulSubType = 0;
						const char * pEnd = NULL;
						if (! ParseType (pValue, pNextLine, & pEnd, ulSubType)) {
							ReportWarning (_p - m_pStart, "Can't parse subtype.");
							ulSubType = 0;
						}
						if (ulSubType > 0xFF) {
							ReportWarning (_p - m_pStart, "Can't parse subtype.");
						} {
							USHORT ushType = rgn.GetType ();
							ushType |= ulSubType;
							rgn.SetType (m_pMap->pTypeSet, ushType, m_Kind);
							if (rgn.pTypeDesc->bUnknown)
								ReportWarning (_p - m_pStart, "Unknown type 0x%x (%s).", ushType, g_strKinds [m_Kind]);
						}
					} else if (HasPrefix (pKey, pNextLine, "Label=", 6)) {
						const size_t cLen = GetLineLen (pValue, pNextLine);
						rgn.strLabel.assign (pValue, cLen);

						if ((rgn.dwFlags & rgn_t::fIndexedCity) && m_strCityName.empty ())
							// NOTE: it is assumed that CityName is equal to Label.
							m_strCityName = rgn.strLabel;
					} else if (
						HasPrefix (pKey, pNextLine, "EndLevel=", 9) ||
						HasPrefix (pKey, pNextLine, "Levels=", 7)
					) {
						unsigned long ulEndLevel = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulEndLevel))
							ReportWarning (_p - m_pStart, "Decimal expected for 'Levels'.");
						else
							rgn.uchEndLevel = ulEndLevel;
					} else if (HasPrefix (pKey, pNextLine, "DirIndicator=", 13)) {
						if (ParseDecU (pValue, pNextLine) != 0)
							rgn.dwFlags |= rgn_t::fDirIndicator;
					} else if (HasPrefix (pKey, pNextLine, "CityName=", 9)) {
						m_strCityName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "RegionName=", 11)) {
						m_strRegionName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "CountryName=", 12)) {
						m_strCountryName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Zip=", 4)) {
						rgn.pExtra->pZip = _GetZip (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "CityIdx=", 8)) {
						unsigned long ulCityIdx = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulCityIdx))
							ReportWarning (_p - m_pStart, "Decimal expected for 'CityIdx'.");
						else {
							if (ulCityIdx - 1 >= m_vecCities.size ())
								ReportWarning (_p - m_pStart, "Unexpected value of 'CityIdx'.");
							else
								rgn.pExtra->pCity = m_vecCities [ulCityIdx - 1];
						}

						// Suppress "CityName=" mechanism.
						m_strCityName.clear ();
					} else if (HasPrefix (pKey, pNextLine, "ZipIdx=", 7)) {
						unsigned long ulZipIdx = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulZipIdx))
							ReportWarning (_p - m_pStart, "Decimal expected for 'ZipIdx'.");
						else {
							if (ulZipIdx - 1 >= m_vecZips.size ())
								ReportWarning (_p - m_pStart, "Unexpected value of 'ZipIdx'.");
							else
								rgn.pExtra->pZip = m_vecZips [ulZipIdx - 1];
						}
					} else if (HasPrefix (pKey, pNextLine, "HighwayIdx=", 11)) {
						unsigned long ulHighwayIdx = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulHighwayIdx))
							ReportWarning (_p - m_pStart, "Decimal expected for 'HighwayIdx'.");
						else {
							if (ulHighwayIdx - 1 >= m_vecHighways.size ())
								ReportWarning (_p - m_pStart, "Unexpected value of 'HighwayIdx'.");
							else
								rgn.pExtra->pHighway = m_vecHighways [ulHighwayIdx - 1];
						}
					} else if (HasPrefix (pKey, pNextLine, "HouseNumber=", 12)) {
						rgn.pExtra->strNumber.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "StreetDesc=", 11)) {
						rgn.pExtra->strStreetDesc.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Phone=", 6)) {
						rgn.pExtra->strPhone.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Fax=", 4)) {
						rgn.pExtra->strFax.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Email=", 6)) {
						rgn.pExtra->strEmail.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "WebPage=", 8)) {
						rgn.pExtra->strWebPage.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Text=", 5)) {
						if (! rgn.pExtra->strDescription.empty ())
							rgn.pExtra->strDescription.append ("\r\n");
						rgn.pExtra->strDescription.append (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "City=Y", 6)) {
						rgn.dwFlags |= rgn_t::fIndexedCity;
						// NOTE: it is assumed that CityName is equal to Label.
						if (m_strCityName.empty ())
							m_strCityName = rgn.strLabel;
					} else if (HasPrefix (pKey, pNextLine, "Background=Y", 12)) {
						if (
							(m_pMap->pTypeSet == & g_tsGarmin || m_pMap->pTypeSet == & g_tsNavitel || m_pMap->pTypeSet == & g_tsRussa) &&
							m_Kind == rkPolygon
						) {
							rgn.SetType (m_pMap->pTypeSet, 0x4b, rkPolygon);
							//rgn.uchEndLevel = 9;
						}
					} else if (HasPrefix (pKey, pNextLine, "DontFind=Y", 10)) {
						rgn.dwFlags |= rgn_t::fDontFind;
					} else if (HasPrefix (pKey, pNextLine, "DontJoin=Y", 10)) {
						rgn.pExtra->bNoJoin = 1;
					} else if (HasPrefix (pKey, pNextLine, "EntryPoint=",  11)) {
						if (! ParseEntryPoint (pValue, pNextLine, (* rgn.pExtra)))
							ReportWarning (_p - m_pStart, "Invalid format of 'EntryPoint'.");
					} else if (
						HasPrefix (pKey, pNextLine, "RouteParam=",  11) ||
						HasPrefix (pKey, pNextLine, "RouteParams=", 12)
					) {
						if (! ParseRouteParam (pValue, pNextLine, (* rgn.pExtra)))
							ReportWarning (_p - m_pStart, "Invalid format of 'RouteParam'.");
					} else if (HasPrefix (pKey, pNextLine, "RouteParamExt=", 14)) {
						rgn.pExtra->bNoThrough = pValue [0] != '0';
					} else if (HasPrefix (pKey, pNextLine, "ManeuverType=", 13)) {
						unsigned long ulManeuverType = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulManeuverType))
							ReportWarning (_p - m_pStart, "Decimal expected for 'ManeuverType'.");
						else if (ulManeuverType >= 256)
							ReportWarning (_p - m_pStart, "'ManeuverType' is above or equals 256.");
						else
							rgn.pExtra->dwManeuverType = ulManeuverType;
					} else if (HasPrefix (pKey, pNextLine, "RoadID=", 7)) {
						unsigned long ulRoadID = 0;
						const char * p;
						if (! ParseDecU (pValue, pNextLine, & p, ulRoadID))
							ReportWarning (_p - m_pStart, "Decimal expected for 'RoadID'.");
						else {
							// TODO: check if RoadID is unique.
							m_RoadID2Object [ulRoadID] = & rgn;
							m_pMap->SetRoadID (rgn, ulRoadID);
						}
					} else if (HasPrefix (pKey, pNextLine, "Nodes", 5)) {
						const char * p = pKey + 5;
						unsigned long ulElement = 0;
						if (! ParseDecU (p, pValue, & p, ulElement)) {
							ReportWarning (_p - m_pStart, "Unexpected format of 'NodesX'.");
						} else {
							if (ulElement >= m_elements.size ())
								ReportWarning (_p - m_pStart, "Unexpected index of element.");
							else {
								rgn_element_t & element = m_elements [ulElement];
								const size_t cPoints = element.points.size ();
								point_extras_t & PointExtras = element.pExtra->PointExtras;
								PointExtras.resize (cPoints);

								p = pValue;
								while (p < pNextLine) {
									// Get opening bracket.
									if (p >= pNextLine && * p != '(')
										break;
									++ p;

									size_t cPoint = -1;
									rgnode_t * const pNode = ParseRGNode (_p, pNextLine, p, cPoints, cPoint);
									if (pNode == NULL)
										break;

									if (cPoint < cPoints)
										PointExtras [cPoint].SetRGNode (pNode, cPoint, cPoints);

									// Get closing bracket.
									if (p >= pNextLine && * p != ')')
										break;
									++ p;
									if (! OmitComma (p, pNextLine))
										break;
								}
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "Nod", 3)) {
						const char * p = pKey + 3;
						unsigned long ulNod = 0;
						if (! ParseDecU (p, pValue, & p, ulNod)) {
							ReportWarning (_p - m_pStart, "Unexpected format of 'NodX'.");
						} else {
							p = pValue;

							const size_t cPoints = m_elements.empty () ? 0 : m_elements [0].points.size ();
							size_t cPoint = -1;
							rgnode_t * const pNode = ParseRGNode (_p, pNextLine, p, cPoints, cPoint);
							if (pNode != NULL) {
								if (! m_elements.empty ()) {
									rgn_element_extra_t & ee = * m_elements [0].pExtra;
									point_extras_t & PointExtras = ee.PointExtras;
									if (PointExtras.empty ())
										PointExtras.resize (cPoints);

									if (cPoint < cPoints)
										PointExtras [cPoint].SetRGNode (pNode, cPoint, cPoints);
								}
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "NumbersExt", 10)) {
						const char * p = pKey + 10;
						unsigned long ulElement = 0;
						if (! ParseDecU (p, pValue, & p, ulElement)) {
							ReportWarning (_p - m_pStart, "Unexpected format of 'NumbersExtX'.");
						} else {
							if (ulElement >= m_elements.size ())
								ReportWarning (_p - m_pStart, "Unexpected index of element.");
							else {
								p = pValue;
								while (p < pNextLine) {
									// Get opening bracket.
									if (p >= pNextLine && * p != '(')
										break;
									++ p;

									ParseNumbers (_p, pNextLine, p, ulElement);

									// Get closing bracket.
									if (p >= pNextLine && * p != ')')
										break;
									++ p;
									if (! OmitComma (p, pNextLine))
										break;
								}
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "Numbers", 7)) {
						const char * p = pKey + 7;
						unsigned long ulNod = 0;
						if (! ParseDecU (p, pValue, & p, ulNod)) {
							ReportWarning (_p - m_pStart, "Unexpected format of 'NumbersX'.");
						} else {
							p = pValue;
							ParseNumbers (_p, pNextLine, p, 0);
						}
					} else {
						//
						// Unsupported parameter.
						//
						rgn.pExtra->strExtra.append (_p, pNextLine);
					}
					break;
				}

				case ST_IMG_ID: {
					m_pMap->bNoHeader = false;
					if (HasPrefix (pKey, pNextLine, "ID=", 3)) {
						m_pMap->Header.strID.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "Name=", 5)) {
						m_pMap->Header.strName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "TypeSet=", 8)) {
						const size_t cLen = GetLineLen (pValue, pNextLine);
						m_pMap->pTypeSet = CTypeSet::FindTypeSet (string_t (pValue, cLen).c_str ());
						if (m_pMap->pTypeSet == & g_tsNull)
							ReportWarning (_p - m_pStart, "Unknown type set.");
					} else if (HasPrefix (pKey, pNextLine, "Levels=", 7)) {
						size_t cLevels = ParseDecU (pValue, pNextLine);
						if (cLevels > 10) {
							cLevels = 10;
							ReportWarning (_p - m_pStart, "Value of 'Levels' is too large. Reset to 10.");
						} else if (cLevels < 2)
							ReportWarning (_p - m_pStart, "Value of 'Levels' is unexpected. Should be at least 2.");
						m_pMap->Levels.resize (cLevels);
						for (size_t cLevel = 0; cLevel < cLevels; ++ cLevel) {
							m_pMap->Levels [cLevel].cZoom = cLevel;
							m_pMap->Levels [cLevel].SetBasicScale (24 - cLevel);
						}
					} else if (HasPrefix (pKey, pNextLine, "Level", 5) && '0' <= pKey [5] && pKey [5] <= '9' && ! ::isalpha (pKey [6])) {
						const int iLevel = pKey [5] - '0';
						if (iLevel < 0 || size_t (iLevel) >= m_pMap->Levels.size ()) {
							ReportWarning (_p - m_pStart, "Level%d is discarded. Please check Levels definition.", iLevel);
							break;
						}

						const size_t cBits = ParseDecU (pValue, pNextLine);
						if (cBits == 0)
							ReportWarning (_p - m_pStart, "Level%d has incorrect value %d.", iLevel, cBits);
						else if (cBits > (m_pMap->pTypeSet == & g_tsGarmin ? 24U : 32U))
							ReportWarning (_p - m_pStart, "Level%d has incorrect value %d.", iLevel, cBits);

						m_pMap->Levels [iLevel].SetBasicScale (cBits);
					} else if (HasPrefix (pKey, pNextLine, "LevelRange", 10) && '0' <= pKey [10] && pKey [10] <= '9') {
						const int iLevel = pKey [10] - '0';
						if (iLevel < 0 || size_t (iLevel) >= m_pMap->Levels.size ()) {
							ReportWarning (_p - m_pStart, "LevelRange%d is discarded. Please check Levels definition.", iLevel);
							break;
						}
						
						const size_t cBits = ParseDecU (pValue, pNextLine);
						if (cBits == 0)
							ReportWarning (_p - m_pStart, "LevelRange%d has incorrect value %d.", iLevel, cBits);
						else if (cBits > (m_pMap->pTypeSet == & g_tsGarmin ? 24U : 32U))
							ReportWarning (_p - m_pStart, "LevelRange%d has incorrect value %d.", iLevel, cBits);
						
						m_pMap->Levels [iLevel].btBitsRangeTo = cBits;
					} else if (HasPrefix (pKey, pNextLine, "Zoom", 4)) {
						const int iLevel = pKey [4] - '0';
						if (iLevel < 0 || size_t (iLevel) >= m_pMap->Levels.size ()) {
							ReportWarning (_p - m_pStart, "Zoom%d is discarded. Please check Levels definition.", iLevel);
							break;
						}

						size_t cZoom = ParseDecU (pValue, pNextLine);
						if (cZoom > 8) {
							ReportWarning (_p - m_pStart, "Zoom%d has incorrect value %d. Reset to 8.", iLevel, cZoom);
							cZoom = 8;
						}

						m_pMap->Levels [iLevel].cZoom = cZoom;
					} else if (HasPrefix (pKey, pNextLine, "Preprocess=", 11)) {
						switch (pValue [0]) {
							case 'N': m_pMap->Header.Preprocess = map_header_t::ppNo; break;
							case 'G': m_pMap->Header.Preprocess = map_header_t::ppGeneralization; break;

							default:
								ReportWarning (_p - m_pStart, "Unexpected value of 'Preprocess'. It will be treated as full preprocessing.");

							case 'F': m_pMap->Header.Preprocess = map_header_t::ppFull; break;
						}
					} else if (HasPrefix (pKey, pNextLine, "Elevation=", 10)) {
						if (pValue [0] == 'm' || pValue [0] == 'M')
							m_pMap->Header.ElevationUnits = unMeters;
						else if (pValue [0] != 'f' && pValue [0] != 'F')
							ReportWarning (_p - m_pStart, "Unknown units for elevation are defined. Feets will be assumed.");
					} else if (HasPrefix (pKey, pNextLine, "TreSize=", 8)) {
						m_pMap->Header.cTreSize = ParseDecU (pValue, pNextLine);
					} else if (HasPrefix (pKey, pNextLine, "TreMargin=", 10)) {
						const char * p;
						float fTreMargin = 0;
						if (! ::ParseReal (pValue, pNextLine, & p, fTreMargin))
							ReportWarning (_p - m_pStart, "'TreMargin' value should be a real number.");
						else
							m_pMap->Header.fTreMargin = fTreMargin;
					} else if (HasPrefix (pKey, pNextLine, "RgnLimit=", 9)) {
						m_pMap->Header.cRgnLimit = ParseDecU (pValue, pNextLine);
						if (m_pMap->Header.cRgnLimit > 1024) {
							ReportWarning (_p - m_pStart, "RgnLimit is too large. Should be < 1024.");
							m_pMap->Header.cRgnLimit = 1024;
						}
					} else if (HasPrefix (pKey, pNextLine, "Transparent=", 12)) {
						m_pMap->Header.bTransparent    = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "POIIndex=", 9)) {
						m_pMap->Header.bPOIIndex       = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "POINumberFirst=", 15)) {
						m_pMap->Header.bPOINumberFirst = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "POIZipFirst=", 12)) {
						m_pMap->Header.bPOIZipFirst    = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "MG=", 3)) {
						m_pMap->Header.bMG             = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "Numbering=", 10)) {
						m_pMap->Header.bNumbering      = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "Routing=", 8)) {
						m_pMap->Header.bRouting        = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "Invisible=", 10)) {
						m_pMap->Header.bInvisible      = pValue [0] == 'Y';
					} else if (HasPrefix (pKey, pNextLine, "Copyright=", 10)) {
						m_pMap->Header.strCopyright.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "CodePage=", 9)) {
						m_pMap->Header.wCodePage = ParseDecU (pValue, pNextLine);
						if (m_pMap->Header.wCodePage != 0 && m_pMap->Header.CodeSchema == bpsAscii7)
							m_pMap->Header.CodeSchema = bpsSBCS;
					} else if (HasPrefix (pKey, pNextLine, "LblCoding=", 10)) {
						switch (ParseDecU (pValue, pNextLine)) {
							case 6:  m_pMap->Header.CodeSchema = bpsAscii7; break;
							case 9:  m_pMap->Header.CodeSchema = bpsSBCS; break;
							case 10: m_pMap->Header.CodeSchema = bpsMBCS; break;
							default: ReportWarning (_p - m_pStart, "Unexpected value of LblCoding. Should be 6, 9 or 10."); break;
						}
					} else if (HasPrefix (pKey, pNextLine, "DefaultCityCountry=", 19)) {
						m_strDefaultCountryName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "DefaultRegionCountry=", 21)) {
						m_strDefaultRegionName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else if (HasPrefix (pKey, pNextLine, "DefaultCityName=", 16)) {
						m_strDefaultCityName.assign (pValue, GetLineLen (pValue, pNextLine));
					} else {
						if (HasPrefix (pKey, pNextLine, "Datum=", 6)) {
							if (HasPrefix (pValue, pNextLine, "W84", 3))
								break;
							else if (HasPrefix (pValue, pNextLine, "Custom:", 7)) {
								if (ParseCustomDatum (pValue + 7, pNextLine))
									break;
								ReportWarning (_p - m_pStart, "Invalid format of the custom datum. No datum convesrion will be made.");
							} else
								ReportWarning (_p - m_pStart, "The specified datum is not supported. This may cause some inaccuracy.");
						}

						// Save all unknown key-value pairs.
						const string_t strKey (_p, GetLineLen (_p, pValue - 1));
						const string_t strValue (pValue, GetLineLen (pValue, pNextLine));
						m_pMap->Header.extra [strKey] = strValue;
					}
					break;
				}

				case ST_CITIES: {
					if (HasPrefix (pKey, pNextLine, "City", 4)) {
						const size_t cIndex = ParseDecU (pKey + 4, pValue);
						if (cIndex != m_cCityIndex)
							ReportWarning (_p - m_pStart, "Unexpected index of 'City'.");
						else {
							++ m_cCityIndex;

							m_pMap->Cities.push_back (city_t ());
							city_t & city = m_pMap->Cities.back ();
							m_vecCities.push_back (& city);

							city.strName.assign (pValue, GetLineLen (pValue, pNextLine));
						}
					} else if (HasPrefix (pKey, pNextLine, "RegionIdx", 9)) {
						const size_t cIndex = ParseDecU (pKey + 9, pValue);
						if (cIndex != m_cCityIndex - 1)
							ReportWarning (_p - m_pStart, "Unexpected index of 'RegionIdx'.");
						const size_t cRegion = ParseDecU (pValue, pNextLine) - 1;
						if (cRegion < m_vecRegions.size ()) {
							city_t & city = m_pMap->Cities.back ();
							city.pRegion = m_vecRegions [cRegion];
						} else
							ReportWarning (_p - m_pStart, "Unexpected value of 'RegionIdx'.");
					}
					break;
				}
				case ST_HIGHWAYS: {
					if (HasPrefix (pKey, pNextLine, "Highway", 7)) {
						const size_t cIndex = ParseDecU (pKey + 7, pValue);
						if (cIndex != m_cHighwayIndex)
							ReportWarning (_p - m_pStart, "Unexpected index of 'Highway'.");
						else {
							++ m_cHighwayIndex;

							m_pMap->Highways.push_back (highway_t ());
							highway_t & highway = m_pMap->Highways.back ();
							m_vecHighways.push_back (& highway);

							highway.strName.assign (pValue, GetLineLen (pValue, pNextLine));
						}
					} else if (HasPrefix (pKey, pNextLine, "RegionIdx", 9)) {
						const size_t cIndex = ParseDecU (pKey + 9, pValue);
						if (cIndex != m_cHighwayIndex - 1)
							ReportWarning (_p - m_pStart, "Unexpected index of 'RegionIdx'.");
						const size_t cRegion = ParseDecU (pValue, pNextLine) - 1;
						if (cRegion < m_vecRegions.size ()) {
							highway_t & highway = m_pMap->Highways.back ();
							highway.pRegion = m_vecRegions [cRegion];
						} else
							ReportWarning (_p - m_pStart, "Unexpected value of 'RegionIdx'.");
					}
					break;
				}
				case ST_ZIPCODES: {
					if (HasPrefix (pKey, pNextLine, "ZipCode", 7)) {
						const size_t cIndex = ParseDecU (pKey + 7, pValue);
						if (cIndex != m_cZipIndex)
							ReportWarning (_p - m_pStart, "Unexpected index of 'ZipCode'.");
						else {
							++ m_cZipIndex;

							m_pMap->Zips.push_back (zip_t ());
							zip_t & zip = m_pMap->Zips.back ();
							m_vecZips.push_back (& zip);
							zip.strName.assign (pValue, GetLineLen (pValue, pNextLine));

							m_name2zip.insert (name2zip_t::value_type (zip.strName, & zip));
						}
					}
					break;
				}
				case ST_REGIONS: {
					if (HasPrefix (pKey, pNextLine, "Region", 6)) {
						const size_t cIndex = ParseDecU (pKey + 6, pValue);
						if (cIndex != m_cRegionIndex)
							ReportWarning (_p - m_pStart, "Unexpected index of 'Region'.");
						else {
							++ m_cRegionIndex;

							m_pMap->Regions.push_back (region_t ());
							region_t & region = m_pMap->Regions.back ();
							m_vecRegions.push_back (& region);

							const char c_strDelimiter [] = "~[0x1d]";
							const char * const pDelimeter = _FindDelimiter (pValue, pNextLine, c_strDelimiter, sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1);
							if (pDelimeter == pNextLine) {
								region.strName.assign (pValue, GetLineLen (pValue, pNextLine));
							} else {
								const char * const pAbbreviation = pDelimeter + sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1;
								region.strName.assign (pValue, pDelimeter - pValue);
								region.strAbbrevName.assign (pAbbreviation, GetLineLen (pAbbreviation, pNextLine));
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "CountryIdx", 10)) {
						const size_t cIndex = ParseDecU (pKey + 10, pValue);
						if (cIndex != m_cRegionIndex - 1)
							ReportWarning (_p - m_pStart, "Unexpected index of 'CountryIdx'.");
						const size_t cCountry = ParseDecU (pValue, pNextLine) - 1;
						if (cCountry < m_vecCountries.size ()) {
							region_t & region = m_pMap->Regions.back ();
							region.pCountry = m_vecCountries [cCountry];
						} else
							ReportWarning (_p - m_pStart, "Unexpected value of 'CountryIdx'.");
					}
					break;
				}
				case ST_COUNTRIES: {
					if (HasPrefix (pKey, pNextLine, "Country", 7)) {
						const size_t cIndex = ParseDecU (pKey + 7, pValue);
						if (cIndex != m_cCountryIndex)
							ReportWarning (_p - m_pStart, "Unexpected index of 'Country'.");
						else {
							++ m_cCountryIndex;

							m_pMap->Countries.push_back (country_t ());
							country_t & country = m_pMap->Countries.back ();
							m_vecCountries.push_back (& country);

							const char c_strDelimiter [] = "~[0x1d]";
							const char * const pDelimeter = _FindDelimiter (pValue, pNextLine, c_strDelimiter, sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1);
							if (pDelimeter == pNextLine) {
								country.strName.assign (pValue, GetLineLen (pValue, pNextLine));
							} else {
								const char * const pAbbreviation = pDelimeter + sizeof (c_strDelimiter)/sizeof (c_strDelimiter [0]) - 1;
								country.strName.assign (pValue, pDelimeter - pValue);
								country.strAbbrevName.assign (pAbbreviation, GetLineLen (pAbbreviation, pNextLine));
							}
						}
					}
					break;
				}
				case ST_RESTRICTION: {
					if (HasPrefix (pKey, pNextLine, "TraffPoints=", 12)) {
						const char * p = pValue;
						unsigned long ulNodeID0 = 0;
						if (! ParseDecU (p, pNextLine, & p, ulNodeID0))
							ReportWarning (p - m_pStart, "Unexpected format of 'TraffPoints'.");
						else {
							OmitComma (p, pNextLine);
							unsigned long ulNodeID1 = 0;
							if (! ParseDecU (p, pNextLine, & p, ulNodeID1))
								ReportWarning (p - m_pStart, "Unexpected format of 'TraffPoints'.");
							else {
								OmitComma (p, pNextLine);
								unsigned long ulNodeID2 = 0;
								if (! ParseDecU (p, pNextLine, & p, ulNodeID2))
									ReportWarning (p - m_pStart, "Unexpected format of 'TraffPoints'.");
								else {
									rgnode_t * const pNode1 = GetRGNode (ulNodeID1);

									rgrestriction_t & restriction = AddRGNodeRestriction (* m_pMap, pNode1);
									restriction.pFromRoad = NULL;
									restriction.pToRoad   = NULL;
									// NOTE: bFromDirection & bToDirection are used as tmp fields here, they are subject to be rewritten below.
									restriction.bFromDirection = reinterpret_cast<DWORD> (GetRGNode (ulNodeID0));
									restriction.bToDirection   = reinterpret_cast<DWORD> (GetRGNode (ulNodeID2));
									m_pLastRestriction = & restriction;
								}
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "TraffRoads=", 11)) {
						const char * p = pValue;
						unsigned long ulRoadID0 = 0;
						if (! ParseDecU (p, pNextLine, & p, ulRoadID0))
							ReportWarning (p - m_pStart, "Unexpected format of 'TraffRoads'.");
						else {
							OmitComma (p, pNextLine);
							unsigned long ulRoadID1 = 0;
							if (! ParseDecU (p, pNextLine, & p, ulRoadID1))
								ReportWarning (pKey - m_pStart, "Unexpected format of 'TraffRoads'.");
							else {
								if (m_pMap->rgrestrictions.empty ())
									ReportWarning (p - m_pStart, "Unexpected order of attributes in [RESTRICT].");
								else {
									if (m_pLastRestriction) {
										// NOTE: the ptr will be adjusted later.
										m_pLastRestriction->pFromRoad = reinterpret_cast<rgn_t *> (ulRoadID0);
										m_pLastRestriction->pToRoad   = reinterpret_cast<rgn_t *> (ulRoadID1);
									}
								}
							}
						}
					} else if (HasPrefix (pKey, pNextLine, "Time=", 5)) {
						if (m_pMap->rgrestrictions.empty ())
							ReportWarning (pKey - m_pStart, "Unexpected order of attributes in [RESTRICT].");						
						else {
							if (m_pLastRestriction) {
								const size_t cLen = GetLineLen (pValue, pNextLine);
								if (cLen > 0)
									m_pLastRestriction->strTime.assign (pValue, cLen);
							}
						}
					}
					break;
				}
			}
			break;
		}
	}

	// Count lines.
	++ m_cFileLine;

	return pNextLine;
}

LoadStatus_t CTxtLoader::Load (const char * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
	if (! CheckAnsiFile (_pData, _cFileSize, false))
		return lsFailed;

	m_pMap   = _pMap;
	m_pStart = _pData;
	m_pEnd   = _pData + _cFileSize;

	// NOTE: it will be set to false later.
	m_pMap->bNoHeader = true;

	m_pMap->pTypeSet              = & g_tsGarmin;
	m_pMap->Header.ElevationUnits = unFeet;
	m_pMap->Header.CodeSchema     = bpsAscii7;
	m_pMap->Header.wCodePage      = 0;

	m_pMap->file_refs.push_back (file_ref_t ());
	m_pFileRef = & m_pMap->file_refs.back ();
	m_pFileRef->strFileName = _strMapFileName;
	m_pFileRef->bAttachment = false;

	m_elements.clear ();
	m_cElements = 0;

	m_vecCountries.clear ();
	m_vecRegions  .clear ();
	m_vecCities   .clear ();
	m_vecZips     .clear ();
	m_vecHighways .clear ();
	m_cCountryIndex = 1;
	m_cRegionIndex  = 1;
	m_cCityIndex    = 1;
	m_cZipIndex     = 1;
	m_cHighwayIndex = 1;

	m_name2city.clear ();
	m_name2zip.clear ();
	m_strDefaultCountryName.clear ();
	m_strDefaultRegionName .clear ();
	m_strDefaultCityName   .clear ();
	m_strCountryName.clear ();
	m_strRegionName .clear ();
	m_strCityName   .clear ();

	m_RoadID2Object.clear ();
	m_NodeID2Node  .clear ();
	m_pLastRestriction = NULL;

	m_pDatumTransformation = std::auto_ptr<CTransformationBase> (NULL);

	m_pComment   = NULL;
	m_pLastSectionEnd = m_pStart;
	m_cPrecOrder = 0;
	m_bHeader    = false;
	m_GroupType  = ST_NONE_START;
	m_Kind       = rkPoint;
	m_cFileLine  = 1;
	m_cOrdinal   = 0;
	m_cPointLen  = 22;

	m_cSetProgress = 0;
	SetProgressStatus (0);

	//
	// Parse lines.
	//

	const char * pLine = _pData;
	do {
		pLine = ParseLine (pLine);
		if (g_bStop)
			return lsInterrupted;
	} while (pLine < _pData + _cFileSize);

	if (m_GroupType != ST_NONE)
		ReportWarning (m_pEnd - m_pStart - 1, "Section is not closed with [END].");
	if (! m_bHeader)
		ReportWarning ("No [IMG ID] section found.");

	SetProgressStatus (50);

	m_pMap->cPrecOrder = m_cPrecOrder > 0 ? m_cPrecOrder : 5;

	//
	// Transform datum, if any.
	//

	if (m_pDatumTransformation.get ()) {
		ReportText ("Performing custom datum transformation to WGS 84...");
		m_pMap->TransformPoints (* m_pDatumTransformation.get ());
	}

	//
	// Post-processing of [RESTRICT]'s.
	//

	rgrestrictions_t::iterator rNext;
	for (rgrestrictions_t::iterator r = m_pMap->rgrestrictions.begin (); r != m_pMap->rgrestrictions.end (); r = rNext) {
		rgrestriction_t & restriction = r->second;

		const RoadID2Object_t::iterator roFrom = m_RoadID2Object.find (reinterpret_cast<DWORD> (restriction.pFromRoad));
		restriction.pFromRoad = roFrom == m_RoadID2Object.end () ? NULL : roFrom->second;

		const RoadID2Object_t::iterator roTo   = m_RoadID2Object.find (reinterpret_cast<DWORD> (restriction.pToRoad));
		restriction.pToRoad = roTo == m_RoadID2Object.end () ? NULL : roTo->second;

		rNext = r;
		++ rNext;

		if (restriction.pFromRoad != NULL && restriction.pToRoad != NULL) {
			const rgnode_t * const pFromNode = reinterpret_cast<const rgnode_t *> (restriction.bFromDirection);
			const rgnode_t * const pToNode   = reinterpret_cast<const rgnode_t *> (restriction.bToDirection);
			bool bFromDirection = false, bToDirection = false;
			if (
				restriction.pFromRoad->GetDirection (pFromNode->dwNodeID, restriction.pNode->dwNodeID, bFromDirection) &&
				restriction.pToRoad  ->GetDirection (restriction.pNode->dwNodeID, pToNode->dwNodeID,   bToDirection)
			) {
				restriction.bFromDirection = bFromDirection;
				restriction.bToDirection   = bToDirection;
			} else {
				ReportWarning ("Some [RESTRICT] refers to incorrect NodeID. It will be ignored.");
				m_pMap->rgrestrictions.erase (r);
			}
		} else {
			ReportWarning ("Some [RESTRICT] refers to incorrect RoadID. It will be ignored.");
			m_pMap->rgrestrictions.erase (r);
		}
	}

	return lsOK;
}

bool CTxtLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (! CheckAnsiFile (_pData, _cFileSize, true))
		return false;

	// Limit the time of validation.
	if (_cFileSize > 65536)
		_cFileSize = 65536;

	size_t cFileLine = 0;
	bool bImgId = false;

	const char * pLine = _pData;
	do {
		const char * const pNextLine = FindNextLine (pLine, _pData + _cFileSize);

		switch (pLine [0]) {
			case '[':
				if (! bImgId)
					bImgId = HasPrefix (pLine + 1, pNextLine, "IMG ID]", 7);
				else
					return HasPrefix (pLine + 1, pNextLine, "END", 3);
		}

		// Count lines.
		++ cFileLine;

		if (cFileLine > 1000)
			return false;

		pLine = pNextLine;
	} while (pLine < _pData + _cFileSize && ! g_bStop);

	return false;
}